﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Primitives;
using WAU.Common;
using WAU.Common.Attributes;
using WAU.Common.Middlewares;
using WAU.DTOs.AppUserModule;
using WAU.DTOs.AppUserModule.Enums;
using WAU.Models.AppUserModels;
using WAU.Models.Framework;
using WAU.Services;
using WAU.Services;
using WAU.Utilities;
using WAU.Utils;
using static WAU.Services.AppUserService;

namespace WAU.Controllers
{
    /// <summary>
    /// 用户控制器
    /// </summary>
    [ApiController]
    [Route("api/[controller]/[action]")]
    public class AppUserController : EssentialController
    {
        /// <summary>
        /// 失效 Token 的缓存
        /// </summary>
        private readonly CacheForComponent _invalidTokenCache;

        /// <summary>
        /// 登录失败的缓存 TAG
        /// </summary>
        private const string LOGIN_FAIL_TAG = "Login_Fail";

        /// <summary>
        /// 登录失败 的缓存
        /// </summary>
        private readonly CacheForComponent _loginFailedCache;

        /// <summary>
        /// 注册验证码缓存 TAG
        /// </summary>
        private const string REGISTER_VERIFY_TAG = "Register_Verify_Code";

        /// <summary>
        /// 注册验证码 的缓存
        /// </summary>
        private readonly CacheForComponent _registerVerifyCodeCache;

        /// <summary>
        /// 忘记密码验证码缓存 TAG
        /// </summary>
        private const string FORGET_PASSWD_VERIFY_TAG = "Forget_Password_Verify_Code";

        /// <summary>
        /// 忘记密码验证码 的缓存
        /// </summary>
        private readonly CacheForComponent _forgetPasswdVerifyCodeCache;

        /// <summary>
        /// 最多可以登录失败多少次
        /// </summary>
        private const int LOGIN_FAILED_TOLERATE_TIMES = 3;

        private readonly IConfiguration _configuration;

        private readonly BasicService essentialService;
        private readonly AppUserService appUserService;
        private readonly JwtHelper jwtHelper;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="configuration"></param>
        /// <param name="distributedCache"></param>
        /// <param name="essentialService"></param>
        /// <param name="appUserService"></param>
        /// <param name="jwtHelper"></param>
        public AppUserController(IConfiguration configuration,
            IDistributedCache distributedCache,
            BasicService essentialService,
            AppUserService appUserService,
            JwtHelper jwtHelper)
        {
            _configuration = configuration;

            _invalidTokenCache = new CacheForComponent(
                InvalidTokenFilterMiddleware.INVALID_TOKEN_CACHE_TAG, distributedCache);
            _loginFailedCache = new CacheForComponent(
                LOGIN_FAIL_TAG, distributedCache);
            _registerVerifyCodeCache = new CacheForComponent(
                REGISTER_VERIFY_TAG, distributedCache);
            _forgetPasswdVerifyCodeCache = new CacheForComponent(
                FORGET_PASSWD_VERIFY_TAG, distributedCache);

            this.essentialService = essentialService;
            this.appUserService = appUserService;
            this.jwtHelper = jwtHelper;
        }

        /// <summary>
        /// Email 验证项
        /// </summary>
        private class EmailVerifyItem
        {
            /// <summary>
            /// Email
            /// </summary>
            public string Email { get; set; }

            /// <summary>
            /// 验证码
            /// </summary>
            public string VerifyCode { get; set; }
        }

        /// <summary>
        /// 请求注册的邮箱验证码
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        [ActionThrottle(30, 1, 5)]
        public async Task<ActionResult> SendRegisterVerifyCodeMail(SendVerifyMailDto dto)
        {
            string sessionId = HttpContext.Session.Id;
            string verifyCode = CommonUtils.GenerateShortUniqueId(6, false);            

            string title = "恒进商城测试版 - 注册";
            string content = $"您的恒进商城测试版注册验证码为【{verifyCode}】，请及时使用，15 分钟后失效！";

            // 发送电子邮件
            ExecResult execResult = await MailUtils.SendEmail(dto.Email, title, content);
            if(execResult.Success)
            {
                // 设置缓存
                EmailVerifyItem emailVerifyItem = new EmailVerifyItem
                { 
                    Email = dto.Email, 
                    VerifyCode = verifyCode 
                };
                string cacheContent = JsonUtils.JsonSerializeForCache(emailVerifyItem);                
                _registerVerifyCodeCache.SetCacheAbsoluteExpire($"{sessionId}", cacheContent, DateTime.Now.AddMinutes(15));

                // 向 Session 设置值，否则服务器不会向浏览器携带 Set-Cookie 请求头
                HttpContext.Session.SetInt32("IsRegister", 1);
            }

            return Ok(new Result
            {
                Success = execResult.Success,
                Data = 30,
                Msg = execResult.ErrorMsg
            }) ;
        }

        /// <summary>
        /// 创建用户
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public ActionResult CreateUser(CreateUserDto dto)
        {
            string sessionId = HttpContext.Session.Id;

            // 判断验证码
            bool cacheResult = _registerVerifyCodeCache.TryGetCacheValue(sessionId, out string cacheContent);
            if (!cacheResult)
            {
                return Ok(new Result { Success = false, Msg = "未发送验证码" });
            }

            EmailVerifyItem item = JsonUtils.JsonDeserializeForCache<EmailVerifyItem>(cacheContent);
            if (item.VerifyCode != dto.VerifyCode)
            {
                return Ok(new Result { Success = false, Msg = "验证码有误" });
            }
            else if (item.Email != dto.Email)
            {
                return Ok(new Result { Success = false, Msg = "邮箱与发送验证码时的邮箱不符" });
            }

            HttpContext.Session.Remove("IsRegister");

            var execResult = appUserService.CreateUser(GlobalConfig.Setting.AdministratorUserId, dto);
            return Ok(new Result 
            { 
                Success = 
                execResult.Success, 
                Msg = execResult.ErrorMsg 
            });
        }

        /// <summary>
        /// 验证码 的缓存 TAG
        /// </summary>
        private const string CAPTACHA_TAG = "captcha_code";

        /// <summary>
        /// 用户登录，失败多次后需要填写验证码
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public ActionResult AppUserLogin(UserLoginDto dto)
        {
            string thisLoginFeatureStr = GetLoginFeature();

            // 缓存中已有先前的失败记录，判断验证码
            bool isFailManyTimes = _loginFailedCache.TryGetCacheValue(thisLoginFeatureStr, out int remainingToleratetimes);
            if (isFailManyTimes && remainingToleratetimes <= 1) // 此时必须输入验证码
            {
                if (string.IsNullOrEmpty(dto.CAPTCHACode) || HttpContext.Session.GetString(CAPTACHA_TAG) != dto.CAPTCHACode)
                {
                    _loginFailedCache.SetCache(thisLoginFeatureStr, --remainingToleratetimes);
                    string captchaImgPath = DrawingUtils.CreateCAPTCHA(out string code);
                    HttpContext.Session.SetString(CAPTACHA_TAG, code);

                    return Ok(new Result
                    {
                        Success = false,
                        Msg = "验证码错误，请检查并重新输入",
                        Data = captchaImgPath
                    });
                }
            }

            ExecResult<LoginUserInfo> loginResult = appUserService.AppUserLogin(dto.UserName, dto.Password);
            if (!loginResult.Success)
            {
                // 第一次失败，设置容忍次数
                if (!isFailManyTimes)
                {
                    _loginFailedCache.SetCache(thisLoginFeatureStr, LOGIN_FAILED_TOLERATE_TIMES);
                }
                // 失败多次，则递减容忍次数
                else
                {
                    _loginFailedCache.SetCache(thisLoginFeatureStr, --remainingToleratetimes);
                    if (remainingToleratetimes <= 1)
                    {
                        string captchaImgPath = DrawingUtils.CreateCAPTCHA(out string code);
                        HttpContext.Session.SetString(CAPTACHA_TAG, code);

                        return Ok(new Result
                        {
                            Success = false,
                            Msg = "失败次数过多，需要验证码",
                            Data = captchaImgPath
                        });
                    }
                }

                return Ok(new Result
                {
                    Success = false,
                    Msg = "用户名或密码错误"
                });
            }

            // 登录成功，清除所有“不良”记录
            HttpContext.Session.Remove(CAPTACHA_TAG);
            _loginFailedCache.RemoveCache(thisLoginFeatureStr);

            string userId = loginResult.Data.UserId;
            List<string> roleCodes = loginResult.Data.RoleCodes;

            string token = jwtHelper.CreateToken(userId, roleCodes,
                                out DateTime tokenExpireTime, out DateTime refreshTokenExpireTime, 
                                dto.IsRememberMe);
            string refreshToken = RefreshTokenUtil.CreateRefreshToken(userId);

            UserLoginResultDto loginResultData = new UserLoginResultDto
            {
                Token = token,
                RefreshToken = refreshToken,
                TokenExpireTime = tokenExpireTime,
                RefreshTokenExpireTime = refreshTokenExpireTime
            };

            return Ok(new Result
            {
                Success = true,
                Msg = "登录成功",
                Data = loginResultData
            });
        }

        /// <summary>
        /// 刷新令牌
        /// </summary>
        /// <param name="refreshTokenDto"></param>
        /// <returns></returns>        
        [HttpPost]
        [AllowAnonymous]
        public ActionResult RefreshToken(RefreshTokenDto refreshTokenDto)
        {
            int validDurationMinutes = _configuration.GetSection("JWT").GetValue<int>("RefreshTokenExpiresMinutes");

            string refreshToken = refreshTokenDto.RefreshToken;
            bool validResult = RefreshTokenUtil.ValidRefreshToken(refreshToken, validDurationMinutes, out string userId);
            if (!validResult)
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "刷新令牌已过期，请重新登录"
                });
            }

            List<string> roleCodes = appUserService.FetchUserRoles(userId).ToList();
            string newToken = jwtHelper.CreateToken(userId, roleCodes,
                                out DateTime newTokenExpireTime, out DateTime newRefreshTokenExpireTime);
            string newRefreshToken = RefreshTokenUtil.CreateRefreshToken(userId);
            UserLoginResultDto loginResultData = new UserLoginResultDto
            {
                Token = newToken,
                RefreshToken = newRefreshToken,
                TokenExpireTime = newTokenExpireTime,
                RefreshTokenExpireTime = newRefreshTokenExpireTime
            };

            return Ok(new Result
            {
                Success = true,
                Msg = "令牌刷新成功",
                Data = loginResultData
            });
        }

        /// <summary>
        /// 刷新令牌工具类
        /// </summary>
        private class RefreshTokenUtil
        {
            private const string TIME_FORMAT_STR = "yyyy-MM-dd HH:mm";

            /// <summary>
            /// 生成刷新Token的密钥
            /// </summary>
            /// <param name="userId"></param>
            /// <returns></returns>
            public static string CreateRefreshToken(string userId)
            {
                return CommonUtils.AESEncrypt(userId + DateTime.Now.ToString(TIME_FORMAT_STR));
            }

            /// <summary>
            /// 验证刷新令牌密钥是否符合规范
            /// </summary>
            /// <param name="refreshToken"></param>
            /// <param name="validDurationMinutes"></param>
            /// <param name="userId"></param>
            /// <returns></returns>
            public static bool ValidRefreshToken(string refreshToken, int validDurationMinutes, out string userId)
            {
                userId = string.Empty;

                string decryptedStr = CommonUtils.AESDecrypt(refreshToken);
                string datetimePartStr = decryptedStr.Substring(decryptedStr.Length - TIME_FORMAT_STR.Length);
                bool parseResult = DateTime.TryParse(datetimePartStr, out DateTime tokenCreateTime);
                if ((tokenCreateTime - DateTime.Now).Minutes > validDurationMinutes)
                {
                    return false;
                }

                userId = decryptedStr.Substring(0, decryptedStr.Length - TIME_FORMAT_STR.Length);
                return true;
            }
        }

        /// <summary>
        /// 刷新验证码
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public ActionResult RefreshCAPTCHA()
        {
            string existCAPTCHA = HttpContext.Session.GetString(CAPTACHA_TAG);
            if (string.IsNullOrEmpty(existCAPTCHA))
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "无需验证码，非法操作"
                });
            }

            string captchaImgPath = DrawingUtils.CreateCAPTCHA(out string code);
            HttpContext.Session.SetString(CAPTACHA_TAG, code);

            return Ok(new Result
            {
                Success = true,
                Msg = "验证码已刷新",
                Data = captchaImgPath
            });
        }

        /// <summary>
        /// 用户退出
        /// </summary>
        /// <returns></returns>        
        [HttpPost]
        [Authorize]
        public ActionResult AppUserLogout()
        {
            InValidUserToken();
            return Ok(new Result
            {
                Success = true,
                Msg = "退出成功"
            });
        }

        /// <summary>
        /// 使用户的 Token 
        /// </summary>
        private void InValidUserToken()
        {
            // 由于该 action 加入了 "Authorize" 特性，如果未登录必然不可能访问，
            // 因此一定可以取得 “Authorization” 字段的值 和 HttpContext.User.Identity.Name 的值
            bool isHasValue = HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues tmpValue);
            if(!isHasValue)
            {
                throw new Exception("未能从 HTTP 的 Authorization 读取到用户的 Token 信息");
            }
            SetTokenForbidden(tmpValue.ToString(), HttpContext.User.Identity.Name);
        }

        /// <summary>
        /// 获取登陆的特征码
        /// </summary>
        /// <returns></returns>
        private string GetLoginFeature()
        {
            // TODO 此处可以添加更多信息来匹配特性
            return string.Format("{0}", HttpContext.Connection.RemoteIpAddress.ToString());
        }

        /// <summary>
        /// 设置 令牌 为过期（与 InvalidTokenFilterMiddleware 中间件配合使用）
        /// </summary>
        /// <param name="toInvalidToken"></param>
        /// <param name="userId"></param>
        private void SetTokenForbidden(string toInvalidToken, string userId)
        {
            int expireMinutes = _configuration.GetSection("JWT").GetValue<int>("ExpiresMinutes");

            // 取 token 的最后 10 位作为缓存 key
            string cacheConent = toInvalidToken.Substring(toInvalidToken.Length - 10);
            _invalidTokenCache.SetCacheAbsoluteExpire(cacheConent, userId, new TimeSpan(0, expireMinutes, 0));
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [Authorize]
        public async Task<ActionResult> GetAppUserInfo()
        {
            string userId = HttpContext.User.Identity.Name;
            GetAppUserInfoResp appUserInfo = await appUserService.GetAppUserInfo(userId);
            if (appUserInfo == null)
            {
                return Ok(new Result { Success = false, Msg = "未找到当前用户信息" });
            }
            else
            {
                return Ok(new Result() { Success = true, Data = appUserInfo });
            }
        }

        /// <summary>
        /// 修改用户信息
        /// </summary>
        /// <param name="sauiReq"></param>
        /// <returns></returns>
        [HttpPost]
        [Authorize]
        public async Task<ActionResult> SetAppUserInfo([FromForm] SetAppUserInfoReq sauiReq)
        {
            string userId = HttpContext.User.Identity.Name;

            string newImageUrl = null;
            var validHeadImageQuery = Request.Form.Files.Where(a => a.Name == "headImage");
            if (validHeadImageQuery.Any())
            {
                List<IFormFile> formFiles = validHeadImageQuery.ToList();
                ExecResult<string> result = CommonUtils.SaveUploadFile(formFiles, "HeadImages");
                if (!result.Success)
                {
                    return Ok(new Result
                    {
                        Success = false,
                        Msg = "头像上传失败"
                    });
                }

                newImageUrl = CommonUtils.ConvertLocalFSPathToURLPart(result.Data);
            }

            bool execResult = await appUserService.SetAppUserInfo(userId, sauiReq, newImageUrl);
            if (execResult)
            {
                return Ok(new Result
                {
                    Success = true,
                    Msg = "修改用户信息成功"
                });
            }
            else
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "修改用户信息失败"
                });
            }
        }

        /// <summary>
        /// 修改用户密码
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [Authorize]
        public async Task<ActionResult> ChangeAppUserPassword(ChangePasswordDto dto)
        {
            ExecResult<AppUser> execResult = await appUserService.ChangeAppUserPassword(CurrentUserId, dto);
            if(execResult.Success)
            {
                // 将现有 Token 失效
                InValidUserToken();

                // 给用户发送提醒邮件
                if (!string.IsNullOrEmpty(execResult.Data.Email))
                {
                    string title = "恒进商城测试版账号提醒";
                    string content = $"您的账户于 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 修改了密码。如果不是您本人的操作，请立即联系客服！";
                    await MailUtils.SendEmail(execResult.Data.Email, title, content);
                }
            }

            return Ok(new Result
            {
                Success = execResult.Success,
                Msg = execResult.ErrorMsg
            });
        }

        /// <summary>
        /// 请求忘记密码的邮箱验证码
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        [ActionThrottle(30, 1, 5)]
        public async Task<ActionResult> SendForgetPasswdVerifyCodeMail(SendVerifyMailDto dto)
        {
            bool isUserExists = await appUserService.IsUserExists(UserIdentityTypeEnum.Email, dto.Email);
            if (!isUserExists)
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = $"邮箱为 {dto.Email} 的用户不存在"
                });
            }

            string sessionId = HttpContext.Session.Id;
            string verifyCode = CommonUtils.GenerateShortUniqueId(6, false);

            string title = "恒进商城测试版 - 忘记密码";
            string content = $"您的恒进商城测试版验证码为【{verifyCode}】，请及时使用，15 分钟后失效！";

            // 发送电子邮件
            ExecResult execResult = await MailUtils.SendEmail(dto.Email, title, content);
            if (execResult.Success)
            {
                // 设置缓存
                EmailVerifyItem emailVerifyItem = new EmailVerifyItem
                {
                    Email = dto.Email,
                    VerifyCode = verifyCode
                };
                string cacheContent = JsonUtils.JsonSerializeForCache(emailVerifyItem);
                _forgetPasswdVerifyCodeCache.SetCacheAbsoluteExpire($"{sessionId}", cacheContent, DateTime.Now.AddMinutes(15));

                // 向 Session 设置值，否则服务器不会向浏览器携带 Set-Cookie 请求头
                HttpContext.Session.SetInt32("IsForgetPasswd", 1);
            }

            return Ok(new Result
            {
                Success = execResult.Success,
                Data = 30,
                Msg = execResult.ErrorMsg
            });
        }

        /// <summary>
        /// 重置密码（忘记密码）
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public async Task<ActionResult> ResetAppUserPassword(ResetPasswordDto dto)
        {
            string sessionId = HttpContext.Session.Id;

            // 判断验证码
            bool cacheResult = _forgetPasswdVerifyCodeCache.TryGetCacheValue(sessionId, out string cacheContent);
            if (!cacheResult)
            {
                return Ok(new Result { Success = false, Msg = "未发送验证码" });
            }

            EmailVerifyItem item = JsonUtils.JsonDeserializeForCache<EmailVerifyItem>(cacheContent);
            if (item.VerifyCode != dto.VerifyCode)
            {
                return Ok(new Result { Success = false, Msg = "验证码有误" });
            }
            else if (item.Email != dto.Email)
            {
                return Ok(new Result { Success = false, Msg = "邮箱与发送验证码时的邮箱不符" });
            }

            HttpContext.Session.Remove("IsForgetPasswd");

            // 设置新的密码
            ExecResult<AppUser> execResult = await appUserService.ResetAppUserPassword(CurrentUserId, dto);
            if (execResult.Success)
            {
                // 给用户发送提醒邮件
                if (!string.IsNullOrEmpty(execResult.Data.Email))
                {
                    string title = "恒进商城测试版账号提醒";
                    string content = $"您的账户于 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 重置了密码。如果不是您本人的操作，请立即联系客服！";
                    await MailUtils.SendEmail(execResult.Data.Email, title, content);
                }
            }

            return Ok(new Result
            {
                Success = execResult.Success,
                Msg = execResult.ErrorMsg
            });
        }
    }
}
